/**
 * Copyright (c) 2023 murenchao
 * taomu is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *       http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package cool.taomu.git

import com.jcraft.jsch.Session
import java.io.File
import java.util.HashMap
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.MergeCommand
import org.eclipse.jgit.api.ResetCommand
import org.eclipse.jgit.api.TransportConfigCallback
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.transport.JschConfigSessionFactory
import org.eclipse.jgit.transport.OpenSshConfig
import org.eclipse.jgit.transport.RefSpec
import org.eclipse.jgit.transport.SshSessionFactory
import org.eclipse.jgit.transport.SshTransport
import org.eclipse.jgit.transport.Transport
import org.eclipse.jgit.transport.URIish
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
import org.eclipse.jgit.util.FS

class GitUtils {
	val String repository = "./repository";

	def static void main(String[] args) {
		var a = new GitUtils();
		// a.cloneByCredentials("http://git.taomu.cool:3000/rcmu/taomu-framework.git", "rcmu", "rcmu2018");
		//a.addRemote("http://git.taomu.cool:3000/rcmu/taomu-framework.git", "gogs");
		//println(a.cloneBySsh("git@gitee.com:iproject/taomu-framework.git","./usecase/id_rsa"))
	}

	def init(String projectName) {
		var file = getRepository(projectName);
		Git.init.setDirectory(file).call;
	}

	def addRemote(String remoteRepository, String name) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		git.remoteAdd().setName(name).setUri(new URIish(remoteRepository)).call();
	}

	def rmRemote(String remoteRepository, String name) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		git.remoteRemove().setRemoteName(name).call();
	}

	def clone(String remoteRepository) {
		var file = getRepository(remoteRepository);
		file.mkdirs;
		return this.clone(remoteRepository,file);
	}
	
	def clone(String remoteRepository,File file){
		return clone(remoteRepository,file,true,null);
	}
	
	def clone(String remoteRepository,File file,boolean isAllBranches,String branchOrTag){
		if(!file.exists){
			file.mkdirs;
		}
		var git = Git.cloneRepository().setURI(remoteRepository).setBare(false)
		if(isAllBranches){
			git.setCloneAllBranches = isAllBranches;
		}else{
			git.cloneAllBranches = isAllBranches;
			git.branch =  branchOrTag;
		}
		git.setDirectory(file);
		return git;
	}
	
	def cloneGitDir(String remoteRepository,File file){
		if(!file.exists){
			file.mkdirs;
		}
		var git = Git.cloneRepository().setURI(remoteRepository).setBare(false)
		git.setGitDir(file);
		git.setCloneAllBranches(true);
		return git;
	}

	private def getRepository(String remoteRepository) {
		var projectName = remoteRepository;
		if (remoteRepository.contains("/")) {
			projectName = remoteRepository.substring(remoteRepository.lastIndexOf("/") + 1)
		}
		var file = new File(#[repository, projectName].join(File.separator));
		if(!file.exists){
			file.mkdirs;
		}
		return file;
	}

	private def getSshConfig(String sshkeyPath) {
		return new TransportConfigCallback() {
			override configure(Transport transport) {
				var sshTransport = transport as SshTransport;
				var jcsf = new JschConfigSessionFactory() {
						override configure(OpenSshConfig.Host host, Session session) {
							session.setConfig("StrictHostKeyChecking", "no");
						}

						override createDefaultJSch(FS fs) {
							var jSch = super.createDefaultJSch(fs);
							jSch.addIdentity(sshkeyPath);
							return jSch;
						}
					} ;
				SshSessionFactory.instance =  new JschConfigSessionFactory() {
						override configure(OpenSshConfig.Host host, Session session) {
							session.setConfig("StrictHostKeyChecking", "no");
						}
						override createDefaultJSch(FS fs) {
							var jSch = super.createDefaultJSch(fs);
							jSch.addIdentity(sshkeyPath);
							return jSch;
						}
				};
				sshTransport.setSshSessionFactory(jcsf);
			}
		}
	}

	def cloneBySsh(String remoteRepository, String sshkeyPath) {
		var clone = this.clone(remoteRepository);
		clone.transportConfigCallback = getSshConfig(sshkeyPath);
		try (var git = clone.call()) {
			var refs = git.lsRemote.setHeads(true).setRemote(remoteRepository).call();
			var StringBuffer s = new StringBuffer();
			for (ref : refs) {
				s.append(ref.getName());
				s.append("\n");
			}
			s.append("clone success")
			return s.toString();
		}

	}

	def cloneByCredentials(String remoteRepository, String username, String password) {
		var clone = this.clone(remoteRepository);
		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password));
		try (var git = clone.call()) {
			var refs = git.lsRemote.setHeads(true).setRemote(remoteRepository).call();
			// var refs = Git.lsRemoteRepository().setHeads(true).setRemote(remote).call();
			var StringBuffer s = new StringBuffer();
			for (ref : refs) {
				s.append(ref.getName());
				s.append("\n");
			}
			s.append("clone success")
			return s.toString();
		}

	}

	def pushBySsh(String remoteRepository, String name, String branch, String sshkeyPath) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		var push = git.push.setRefSpecs(new RefSpec(branch)).setRemote(name);
		push.transportConfigCallback = getSshConfig(sshkeyPath);
		var infos = push.call()
		var buffer = new StringBuffer();
		for (info : infos) {
			buffer.append(info.messages);
			buffer.append("\n")
		}
		return buffer.toString();
	}

	def pushByCredentials(String remoteRepository,String name, String branch, String username, String password) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		var push = git.push.setRefSpecs(new RefSpec(branch)).setRemote(name);
		push.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password));
		var infos = push.call()
		var buffer = new StringBuffer();
		for (info : infos) {
			buffer.append(info.messages);
			buffer.append("\n")
		}
		return buffer.toString();
	}

	def fetchBySsh(String remoteRepository, String sshkeyPath) {
		var file = getRepository(remoteRepository);
		var refs = Git.lsRemoteRepository().setHeads(true).setRemote(remoteRepository).call();
		var log = new StringBuffer("fetch:\n");
		for (ref : refs) {
			var String refStr = ref.getName();
			var fetchCommand = Git.open(file).fetch().setRemote(remoteRepository).setRefSpecs(
				#[new RefSpec(refStr + ":" + refStr)])
			fetchCommand.transportConfigCallback = getSshConfig(sshkeyPath);
			var result = fetchCommand.call();
			log.append(result.getMessages());
			log.append("\n");
		}
		log.append("Fetch success")
		return log.toString();
	}

	def fetchByCredentials(String remoteRepository, String branch, String username, String password) {

		var file = getRepository(remoteRepository);
		var refs = Git.lsRemoteRepository().setHeads(true).setRemote(remoteRepository).call();
		var log = new StringBuffer("fetch:\n");
		for (ref : refs) {
			var String refStr = ref.getName();
			var fetchCommand = Git.open(file).fetch().setRemote(remoteRepository).setRefSpecs(
				#[new RefSpec(refStr + ":" + refStr)])
			fetchCommand.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password));
			var result = fetchCommand.call();
			log.append(result.getMessages());
			log.append("\n");
		}
		log.append("Fetch success")
		return log.toString();
	}

	def commit(String remoteRepository, String[] files, String msg) {
		var file = getRepository(remoteRepository);
		val git = Git.open(file);
		files.forEach [
			git.add().addFilepattern(it).call();
		]
		git.commit.setMessage(msg).call();
	}

	def commitAll(String remoteRepository, String msg) {
		var file = getRepository(remoteRepository);
		val git = Git.open(file);
		git.add().addFilepattern(".").call();
		git.commit.setMessage(msg).call();
	}

	def status(String remoteRepository) {
		var map = new HashMap<String, String>();
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		var status = git.status.call();
		map.put("Added", status.added.toString());
		map.put("Changed", status.changed.toString());
		map.put("Conflicting", status.conflicting.toString());
		map.put("ConflictingStageState", status.conflictingStageState.toString());
		map.put("IgnoredNotInIndex", status.ignoredNotInIndex.toString());
		map.put("Missing", status.missing.toString());
		map.put("Modified", status.modified.toString());
		map.put("Removed", status.removed.toString());
		map.put("UntrackedFiles", status.untracked.toString());
		map.put("UntrackedFolders", status.untrackedFolders.toString());
		return map;
	}

	def branchCreate(String remoteRepository, String name) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		git.branchCreate.setName(name).call();
	}

	def branchDelete(String remoteRepository, String[] names) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		git.branchDelete.setBranchNames(names).call();
	}

	def branchRename(String remoteRepository, String oldName, String newName) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		git.branchRename.setNewName(newName).setOldName(oldName).call;
	}

	def branchList(String remoteRepository) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		val result = newArrayList;
		git.branchList.call.forEach [
			result.add(it.name);
		]
		return result;
	}

	def checkout(String remoteRepository, String name) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		var ck = git.checkout.name = name;
		ck.call;
	}

	def merge(String remoteRepository, String srcName, String mergeBranch, String mgs) {
		var file = getRepository(remoteRepository);
		var git = Git.open(file);
		var ref = git.checkout.setName(srcName).call();
		git.checkout.setName(mergeBranch).call();
		var merge = git.merge.include(ref).setCommit(true).fastForward = MergeCommand.FastForwardMode.NO_FF
		merge.message = mgs;
		merge.call;
	}

	def pullByCredentials(String remoteRepository, String remoteBranch, String username, String password) {
		var file = getRepository(remoteRepository);
		val git = Git.open(file);
		var pull = git.pull.remoteBranchName = remoteBranch;
		pull.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password));
		pull.call;
	}

	def pullBySsh(String remoteRepository, String remoteBranch, String sshkeyPath) {
		var file = getRepository(remoteRepository);
		val git = Git.open(file);
		var pull = git.pull.remoteBranchName = remoteBranch;
		pull.transportConfigCallback = getSshConfig(sshkeyPath);
		pull.call;
	}

	def reset(String remoteRepository, String objectId) {
		var file = getRepository(remoteRepository);
		val git = Git.open(file);
		var walk = new RevWalk(git.repository);
		var objId = git.repository.resolve(objectId);
		var revCommit = walk.parseCommit(objId);
		var vision = revCommit.getParent(0).getName();
		git.reset.setMode(ResetCommand.ResetType.HARD).setRef(vision).call;
	}

	def revert(String remoteRepository, String commitId) {
		var file = getRepository(remoteRepository);
		val git = Git.open(file);
		var walk = new RevWalk(git.repository);
		var revCommit = walk.parseCommit(ObjectId.fromString(commitId));
		git.revert.include(revCommit).call();
	}

}
